from util import config
import logging.config
logging.config.fileConfig('logging.config')

from async import asyncmanager
from db import db
from net import network, queues
from presence import ps

from fvutil import broker, accounts, fvcon
from fvutil.managers import audio, text, group, poll, shopping, timeline, presencewrapper, tivo
from ui import flashinterface
from audio import mixerinterface

import os
import gobject

logger = logging.getLogger('fluidvoice')

import cPickle as pickle
class FluidVoice:
    
    class Control:
        
        def __init__(self, iface, port, presence):
            self.__presence = presence
            self.__queue = queues.Queue()
            self.__socket = network.Unicast(iface, config.get_int('fv', 'control-port'), self.__queue, self.__receive)
            self.__type_receivers = {}
        
        def send(self, control, id):
            """
                Sends a control message to a node with the given id
                @param control: An object that represents some control information
                @type control: whatever
                @param id: The id of the direct neighbor to send to
                @type id: int
            """
            neighbor = self.__presence.get_neighbor(id)
            addr = (neighbor.ip, neighbor.meta[config.get_int('fv', 'control-port-key')])
            data = pickle.dumps(control, pickle.HIGHEST_PROTOCOL)
            logger.debug('Sending control %s to address %s', control, addr)
            self.__queue.put(data, addr)
        
        def is_neighbor(self, id):
            neighbors = self.__presence.get_neighbors()
            for n in neighbors:
                if n.pid == id:
                    return True
            return False
        
        def register_type_receiver(self, type, recv_func):
            type_name = type.__name__
            if type_name not in self.__type_receivers:
                self.__type_receivers[type_name] = []
            self.__type_receivers[type_name].append(recv_func)
            
        def __receive(self, data, addr):
            control = pickle.loads(data)
            control_type = control.__class__.__name__
            receivers = self.__type_receivers.get(control_type, None)
            if receivers:
                for receiver in receivers:
                    receiver(control)
            else:
                raise ValueError, "Control channel got a type it doesn't know how to handle: %s" % (control_type,)
            
    # TODO: Get rid of my-id and my-name as we use proper login system
    def __init__(self, iface, config_files=[], my_id=None, my_name=None, flash_local=False):
        logger.critical('----------------- START RUN -----------------')
        default_config = os.path.join(os.path.dirname(__file__), 'fv.config')
        config.add_file(default_config)
    
        if config_files:
            config.add_files(config_files)
            
        print 'Initializing:',my_id,my_name
        # Initialize all the modules.
        self.__presence = ps.PresenceService(iface, my_id, my_name, standalone=False, config_files=config_files)
        # Add meta about me
        self.__presence.add_meta(fvcon.STATUS, fvcon.ONLINE)
        self.__presence.add_meta(fvcon.PRIORITY, fvcon.PRIORITY_LOW)
        # TODO: Do we need this?
        self.__presence.add_meta(fvcon.AUDIO_PORT, 'audio port')
        self.__presence.add_meta(fvcon.SSRC, None)
        self.__presence.add_meta(config.get_int('fv', 'control-port-key'), config.get_int('fv', 'control-port'))
               
        self.__control = FluidVoice.Control(iface, config.get_int('fv', 'control-port'), self.__presence)
        
        self.__am = asyncmanager.AsyncManager(iface, self.__presence, config_files=config_files)
        
        self.__accounts = accounts.Accounts.instance()
        
        self.__mixer = mixerinterface.MixerInterface(network.get_broadcast_addr(iface))
        
        # TODO: Remove this:
        if not self.__accounts.user_exists('Guest'):
            self.__accounts.create_user('Guest', '')
            
        self.__presence_wrapper = presencewrapper.PresenceWrapper(self.__presence)
        self.__text = text.TextManager(self.__am, my_id)
        self.__audio = audio.AudioManager(self.__am, my_id, self.__mixer)
        self.__group = group.GroupManager(self.__am, my_id)
        self.__poll = poll.PollManager(self.__am, my_id, self.__mixer)
        self.__shopping = shopping.ShoppingManager(self.__am, my_id)
        self.__tivo = tivo.TivoManager(self.__am, my_id, self.__mixer, self.__control)
        
        self.__broker = broker.ObjectBroker(self.__presence_wrapper, self.__group, self.__audio, 
                                            self.__text, self.__poll, self.__shopping, self.__tivo)
        
        self.__timeline = timeline.Timeline(my_id)

        
        self.__flash = flashinterface.FlashInterface(iface, local=flash_local)
        
        # Wire up callbacks, events
        self.__am.connect('message-received', self.__broker.on_receive_object)
        
        #self.__accounts.connect('login-result', self.on_login_result)
        self.__flash.connect('login', self.__on_login)
        
        self.__flash.connect('edit_object', self.__mixer.onChange)
        # Async audio
        self.__flash.connect('start_record' , self.__mixer.onStartRecord)
        self.__flash.connect('stop_record', self.__mixer.onStopRecord)
        self.__flash.connect('start_play', self.__mixer.onStartPlay)
        self.__flash.connect('stop_play', self.__mixer.onStopPlay)

        self.__flash.connect('poll_start_record', self.__poll.on_start_record)
        self.__flash.connect('poll_stop_record', self.__poll.on_stop_record)
        self.__flash.connect('poll_start_play', self.__poll.on_start_play)
        self.__flash.connect('poll_stop_play', self.__poll.on_stop_play)
        
        self.__flash.connect('send_object', self.__broker.on_send_object)
         
        self.__flash.connect('get_messages', self.__timeline.get_messages)
        self.__flash.connect('get_message_count', self.__timeline.get_message_count)
        
        self.__presence.connect('neighbor-online', self.__neighbor_online)
        self.__presence.connect('neighbor-update', self.__neighbor_update)
        self.__presence.connect('neighbor-offline', self.__neighbor_offline)
        
        self.__mixer.connect('my_ssrc', self.__on_my_SSRC)
        self.__mixer.connect('started_record', self.__flash.startedRecord)
        self.__mixer.connect('stopped_record', self.__flash.stoppedRecord)
        self.__mixer.connect('started_play', self.__flash.startedPlay)
        self.__mixer.connect('stopped_play', self.__flash.stoppedPlay)
        
        self.__audio.connect('sent_audio', self.__flash.sentObject)
        self.__audio.connect('received_audio', self.__flash.updateObject)
        self.__audio.connect('received_audio', self.__mixer.onAddInput)

        # Asynch text UI callbacks
        self.__text.connect('created_text', self.__flash.sentObject)
        self.__text.connect('received_text', self.__flash.updateObject)

        self.__shopping.connect('created_list', self.__flash.sentObject)
        self.__shopping.connect('received_list', self.__flash.updateObject)
       
        # Asynch poll UI callbacks
        self.__poll.connect('created_poll', self.__flash.sentObject)
        self.__poll.connect('received_poll', self.__flash.updateObject)
        self.__poll.connect('submitted_pollvote', self.__flash.sentObject)
        self.__poll.connect('received_pollresults', self.__flash.resultsReady)
        
        # audio requests from UI
        self.__flash.connect('push_to_talk', self.__mixer.onMIC)
        # TODO: What is this?
        #self.__flash.connect('push_to_talk', self.__presence.onChangeStatus)
        #self.__mixer.connect( 'ack_push_to_talk', self.__flash.onAckPushToTalk)
        
        # UI request to open an object
        self.__flash.connect('open_object', self.__broker.on_open_object)
        #self._broker.connect('return_object', self.__flash.returnObject)
        
        # group related methods
        self.__flash.connect('new_group', self.__group.on_new_group)
        self.__flash.connect('add_user_to_group',self.__group.on_add_user_to_group)
        self.__flash.connect('del_user_from_group', self.__group.on_del_user_from_group )
        self.__flash.connect('show_group', self.__group.on_show_group )
        self.__flash.connect('del_group', self.__group.on_del_group )
        self.__flash.connect('update_group_name', self.__group.on_update_group_name) 

        # Tivo-related methods
        self.__flash.connect('start_tivo', self.__tivo.on_start_tivo)
        self.__flash.connect('stop_tivo', self.__tivo.on_stop_tivo)
        self.__flash.connect('exclude_me_from_tivo', self.__tivo.on_exclude_me_from_tivo)
        
        self.__tivo.connect('sent_tivo', self.__flash.sentObject)
        self.__tivo.connect('received_tivo', self.__flash.updateObject)
        self.__tivo.connect('started_tivo', self.__flash.onStartedTivo)
        self.__tivo.connect('warning_tivo', self.__flash.onWarningTivo)
                    
        # response events and new events in group service
        #self._gs.connect('return_groups', self.__flash.sendGroups )
        self.__group.connect('group_appeared', self.__flash.groupAppeared )
        #self._group.addLookupNameCallback( self._presence.getUserName )

        network.start()
    
    def run(self):
        # Start main application loop
        gobject.threads_init()
        import thread
        thread.start_new_thread(self.__text_control,())
        self._mainloop = gobject.MainLoop()
        self._mainloop.run()
        
        
    def __text_control(self):
        p = self.__presence
        f = self.__flash
        c = self.__control
        import controlmsgs
        msg = controlmsgs.StartTivo(12345, [2,3])
        start = pickle.dumps(msg, pickle.HIGHEST_PROTOCOL)
        msg = controlmsgs.ExcludeFromTivo(12345, 2)
        rec = c._Control__receive
        exclude = pickle.dumps(msg, pickle.HIGHEST_PROTOCOL)
        
        while True:
            cmd = raw_input('>>')
            try:
                exec(cmd)
            except Exception, e:
                print dir(e)
                print e
        
        
    def stop(self):
        pass
    
    # Event handlers
    def __on_my_SSRC(self, obj, data):
        raise NotImplementedError
    
    def __on_login(self, flash, username, password):
        success = self.__accounts.authenticate(username, password)
        print success
        if success:
            allusers = self.__presence.get_all_known_users()
            allobjects = self.__broker.get_all_objects()
            flash.loginAck(username, allusers+allobjects)
            print 'fluidvoice: Login ack',username,allusers+allobjects
        else:
            print 'fluidvoice: Username, password pair incorrect'
       
    # CRAP!
    # CRAP!!
    # CRAP!!!
    def __neighbor_online(self, obj, user, new):
        print 'neighbor_online',user.pid
        if isinstance(user, ps.UserPresence):
            presence_type = fvcon.FV
        else:
            presence_type = fvcon.SERVICE
        if new:
            subcommand = fvcon.ADD
        else:
            subcommand = fvcon.UPDATE
            
        print 'new?',new
        presence_dict = {'subcommand':subcommand,
                         'id': user.pid, 
                         'label':user.name,
                         'type':presence_type, 
                         'status':user.meta[fvcon.STATUS],
                         'priority':user.meta[fvcon.PRIORITY],
                         'address': user.ip,
                         'port':user.meta[fvcon.AUDIO_PORT],
                         'ssrc': user.meta[fvcon.SSRC]
                         }
        print 'SSRC!',presence_dict['ssrc']
        self.__flash.updateObject(obj, presence_dict)
        self.__mixer.onAddInput(obj, presence_dict)
        
    def __neighbor_update(self, obj, user):
        print 'neighbor_update',user.pid
        if isinstance(user, ps.UserPresence):
            presence_type = fvcon.FV
        else:
            presence_type = fvcon.SERVICE
        presence_dict = {'subcommand':fvcon.UPDATE,
                         'id': user.pid, 
                         'label':user.name,
                         'type':presence_type, 
                         'status':user.meta[fvcon.STATUS],
                         'priority':user.meta[fvcon.PRIORITY],
                         'address': user.ip,
                         'port':user.meta[fvcon.AUDIO_PORT],
                         'ssrc': user.meta[fvcon.SSRC]
                         }
        self.__flash.updateObject(obj, presence_dict)
        print 'ON ADD INPUT'
        self.__mixer.onAddInput(obj, presence_dict)
        
        
    def __neighbor_offline(self, obj, user):
        print 'neighbor_offline',user.pid
        if isinstance(user, ps.UserPresence):
            presence_type = fvcon.FV
        else:
            presence_type = fvcon.SERVICE
        presence_dict = {'subcommand':fvcon.UPDATE,
                         'id': user.pid, 
                         'label':user.name,
                         'type':presence_type, 
                         'status':fvcon.OFFLINE,
                         'priority':user.meta[fvcon.PRIORITY],
                         'address': user.ip,
                         'port':user.meta[fvcon.AUDIO_PORT],
                         'ssrc': user.meta[fvcon.SSRC]
                         }
        self.__flash.updateObject(obj, presence_dict)
        self.__mixer.onOffline(obj, presence_dict)
    
    def __on_my_SSRC(self, obj, ssrc):
        print 'Setting my SSRC',ssrc
        self.__presence.add_meta(fvcon.SSRC, ssrc)
        
if __name__ == "__main__":
    import getopt
    import sys
    from util import device
    
    o,a = getopt.getopt(sys.argv[1:], 'hc:f:ai:p:u:d:') # : specifies we expect a value after c and f
    opts = {}
    for k,v in o:
        opts[k] = v
    
    # If help has been asked, display usage and exit
    if '-h' in opts:
        usage()
        sys.exit(0)
    
    if '-i' in opts:
        ifname = opts['-i']
    else:
        ifname = "eth1"
        
    if '-a' in opts:
        common.SetRCVR(ifname)
        
    if '-c' in opts:
        config_file = opts['-c']
    else:
        config_file = None
    
    if '-f' in opts:
        val = opts['-f']
        if val == "remote":
            flash_local = False
        elif val == "local":
            flash_local = True
        else:
            print "Unrecognized value given for -f argument:",val
            usage()
            sys.exit(1)
            
    if '-p' in opts:
        file = opts['-p']
        f = open(file)
        contents = f.read()
        f.close()
        username, user_id = contents.split(',')
    else:
        if '-u' not in opts:
            print 'You must specify a username with the -u flag'
            sys.exit(1)
        else:
            username = opts['-u']
        
        if '-d' not in opts:
            print 'You must specify a user id with the -d flag'
            sys.exit(1)
        else:
            user_id = opts['-d']
        # check to see if directories are ready
        #checksetup.setupDirs()
    
    user_id = int(user_id)
    
    if config_file:
        config_files = [config_file]
    else:
        config_files = []
        
    if device.current() == device.WINDOWS:
        config_files.append('windows-overrides.config')
    elif device.current() == device.NOKIA_TABLET:
        config_files.append('nokia-overrides.config')

    app = FluidVoice(ifname, config_files, my_id=user_id, my_name=username, flash_local=flash_local)
    try:
        app.run()
    except KeyboardInterrupt:
        app.stop()
        print "Crtl+C pressed. Shutting down."